use proc_macro::{Group, TokenStream, TokenTree};
use proc_macro2::Span;
use quote::{quote, ToTokens};
use syn::{parse_macro_input, Attribute, Data, DeriveInput, Fields, Ident, Variant};

struct EnumItem {
    name: String,
    code: String,
    label: String,
}

pub fn derive_fn(input: TokenStream) -> TokenStream {
    // 解析输入的枚举定义
    let input = parse_macro_input!(input as DeriveInput);
    let name_ident = &input.ident;
    let variants = match input.data {
        Data::Enum(ref data_enum) => &data_enum.variants,
        _ => panic!("EnumExt can only be derived for enums"),
    };

    let enum_items = variants
        .iter()
        .map(|variant| {
            let variant_ident = &variant.ident;
            println!("variant_ident: {:?}", variant_ident);

            let line = variant
                .attrs
                .iter()
                .find(|attr| attr.path().is_ident("attrs"));

            let Some(attr) = line else {
                return None;
            };

            get_enum_values(attr).map(|item| {
                let code = match item.code.as_str() {
                    "" => variant_ident.to_string(),
                    _ => item.code,
                };

                EnumItem {
                    name: variant_ident.to_string(),
                    code: code.clone(),
                    ..item
                }
            })
        })
        .collect::<Vec<_>>();

    let match_arms = enum_items
        .iter()
        .filter(|s| s.is_some())
        .map(|s| s.as_ref().unwrap())
        .map(|item| {
            let EnumItem { name, label, code } = item;
            let variant_ident = Ident::new(&name, Span::call_site());
            let label_arm = quote! {
                #name_ident::#variant_ident => #label.to_string(),
            };

            let code_arm = quote! {
                #name_ident::#variant_ident => #code.to_string(),
            };

            let name_arm = quote! {
                #name_ident::#variant_ident => #name.to_string(),
            };

            (name_arm, label_arm, code_arm)
        })
        .collect::<Vec<_>>();

    let mut name_match_arms = vec![];
    let mut label_match_arms = vec![];
    let mut code_match_arms = vec![];

    for (name_arm, label_arm, code_arm) in match_arms {
        name_match_arms.push(name_arm);
        label_match_arms.push(label_arm);
        code_match_arms.push(code_arm);
    }

    let expanded_name_method = quote! {
        impl #name_ident {
            pub fn name(&self) -> String {
                match self {
                    #(#name_match_arms)*
                }
            }
        }
    };

    let expanded_label_method = quote! {
        impl #name_ident {
            pub fn label(&self) -> String {
                match self {
                    #(#label_match_arms)*
                }
            }
        }
    };

    let expanded_code_method = quote! {
        impl #name_ident {
            pub fn code(&self) -> String {
                match self {
                    #(#code_match_arms)*
                }
            }
        }
    };

    let expanded_to_json_method = quote! {
        impl #name_ident {
            pub fn to_json(&self) -> String {
              format!(
                    r#"{{ "code": "{}", "name": "{}", "label": "{}" }}"#,
                    self.code(),
                    self.name(),
                    self.label(),
                )
             }
        }
    };

    let expanded = quote! {
        #expanded_label_method
        #expanded_code_method
        #expanded_name_method
        #expanded_to_json_method
    };

    expanded.into()
}

fn get_enum_values(attr: &Attribute) -> Option<EnumItem> {
    let groups = attr
        .to_token_stream()
        .into_iter()
        .find(|s| matches!(s, proc_macro2::TokenTree::Group(_)));

    let Some(proc_macro2::TokenTree::Group(group)) = groups else {
        return None;
    };

    let Some(proc_macro2::TokenTree::Group(group)) = group.stream().into_iter().nth(1) else {
        return None;
    };

    let idents = group
        .stream()
        .into_iter()
        .filter(|s| matches!(s, proc_macro2::TokenTree::Ident(_)))
        .map(|s| match s {
            proc_macro2::TokenTree::Ident(id) => id,
            _ => unreachable!(),
        })
        .collect::<Vec<_>>();

    let literals = group
        .stream()
        .into_iter()
        .filter(|s| matches!(s, proc_macro2::TokenTree::Literal(_)))
        .map(|s| match s {
            proc_macro2::TokenTree::Literal(id) => id,
            _ => unreachable!(),
        })
        .collect::<Vec<_>>();

    let mut item = EnumItem {
        name: "".to_string(),
        code: "".to_string(),
        label: "".to_string(),
    };

    for (index, ident) in idents.into_iter().enumerate() {
        let name = ident.to_string();
        let value = literals.get(index).unwrap().to_string();
        let value = value.replace("\"", "");

        if name == "label" {
            item.label = value;
        } else if name == "code" {
            item.code = value;
        }
    }
    Some(item)
}
